﻿
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;
using System.Linq.Expressions;
using System.Diagnostics;
using System.Reflection;
using System.Collections.ObjectModel;

namespace TextFlow.Engine
{
    internal class ExpressionSerializerSelector : SurrogateSelector
    {
        private ExpressionSerializer _surrogate = new ExpressionSerializer();

        public override ISerializationSurrogate GetSurrogate( Type type, StreamingContext context, out ISurrogateSelector selector )
        {
            if ( type == typeof( Expression ) || type.IsSubclassOf( typeof( Expression ) ) )
            {
                selector = this;

                return _surrogate;
            }
            else
            {
                return base.GetSurrogate( type, context, out selector );
            }
        }
    }

    internal class ExpressionSerializer : ISerializationSurrogate
    {
        public ExpressionSerializer()
        {
        }

        public void GetObjectData( object obj, SerializationInfo info, StreamingContext context )
        {
            if ( !( obj is Expression ) )
            {
                throw new InvalidOperationException( "Only Expression serialization supported." );
            }

            Expression baseExpr = obj as Expression;

            info.AddValue( "__nodeType", baseExpr.NodeType, typeof( ExpressionType ) );
            info.AddValue( "__type", baseExpr.Type, typeof( Type ) );

            switch ( baseExpr.NodeType )
            {
                case ExpressionType.Call:

                    MethodCallExpression methodCall = baseExpr as MethodCallExpression;

                    info.AddValue( "__instance", methodCall.Object, typeof( Expression ) );
                    info.AddValue( "__method", methodCall.Method, typeof( MethodInfo ) );
                    info.AddValue( "__args", methodCall.Arguments, typeof( ReadOnlyCollection<Expression> ) );

                    break;

                case ExpressionType.Constant:

                    ConstantExpression constant = baseExpr as ConstantExpression;

                    info.AddValue( "__value", constant.Value );

                    break;

                case ExpressionType.Parameter:

                    ParameterExpression parm = baseExpr as ParameterExpression;

                    info.AddValue( "__name", parm.Name );

                    break;

                case ExpressionType.AddChecked:
                case ExpressionType.SubtractChecked:
                case ExpressionType.MultiplyChecked:
                case ExpressionType.Divide:
                case ExpressionType.And:
                case ExpressionType.Or:
                case ExpressionType.Equal:
                case ExpressionType.NotEqual:
                case ExpressionType.GreaterThan:
                case ExpressionType.GreaterThanOrEqual:
                case ExpressionType.LessThan:
                case ExpressionType.LessThanOrEqual:

                    BinaryExpression binary = baseExpr as BinaryExpression;

                    info.AddValue( "__lhs", binary.Left, typeof( Expression ) );
                    info.AddValue( "__rhs", binary.Right, typeof( Expression ) );

                    break;

                case ExpressionType.Not:
                case ExpressionType.NegateChecked:

                    UnaryExpression unary = baseExpr as UnaryExpression;

                    info.AddValue( "__operand", unary.Operand, typeof( Expression ) );

                    break;

                case ExpressionType.ConvertChecked:

                    UnaryExpression unary2 = baseExpr as UnaryExpression;

                    info.AddValue( "__operand", unary2.Operand, typeof( Expression ) );
                    info.AddValue( "__method", unary2.Method, typeof( MethodInfo ) );

                    break;

                default:
                    Debug.Fail( string.Format( "Expression type not supported: {0}", baseExpr.NodeType.ToString() ) );
                    break;
            }
        }

        public object SetObjectData( object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector )
        {
            Expression result = null;

            ExpressionType nodeType = (ExpressionType) info.GetValue( "__nodeType", typeof( ExpressionType ) );

            switch ( nodeType )
            {
                case ExpressionType.Call:
                    {
                        Expression instance = (Expression) info.GetValue( "__instance", typeof( Expression ) );
                        MethodInfo method = (MethodInfo) info.GetValue( "__method", typeof( MethodInfo ) );
                        ReadOnlyCollection<Expression> args = (ReadOnlyCollection<Expression>) info.GetValue( "__args", typeof( ReadOnlyCollection<Expression> ) );

                        result = Expression.Call( instance, method, args );

                        break;
                    }
                case ExpressionType.Constant:
                    {
                        Type t = (Type) info.GetValue( "__type", typeof( Type ) );
                        object val = info.GetValue( "__value", t );

                        result = Expression.Constant( val, t );

                        break;
                    }
                case ExpressionType.Parameter:
                    {
                        Type t = (Type) info.GetValue( "__type", typeof( Type ) );
                        string name = info.GetString( "__name" );

                        result = Expression.Parameter( t, name );

                        break;
                    }
                case ExpressionType.AddChecked:
                    {
                        Expression lhs = (Expression) info.GetValue( "__lhs", typeof( Expression ) );
                        Expression rhs = (Expression) info.GetValue( "__rhs", typeof( Expression ) );

                        result = Expression.AddChecked( lhs, rhs );

                        break;
                    }
                case ExpressionType.SubtractChecked:
                    {
                        Expression lhs = (Expression) info.GetValue( "__lhs", typeof( Expression ) );
                        Expression rhs = (Expression) info.GetValue( "__rhs", typeof( Expression ) );

                        result = Expression.SubtractChecked( lhs, rhs );

                        break;
                    }
                case ExpressionType.MultiplyChecked:
                    {
                        Expression lhs = (Expression) info.GetValue( "__lhs", typeof( Expression ) );
                        Expression rhs = (Expression) info.GetValue( "__rhs", typeof( Expression ) );

                        result = Expression.MultiplyChecked( lhs, rhs );

                        break;
                    }
                case ExpressionType.Divide:
                    {
                        Expression lhs = (Expression) info.GetValue( "__lhs", typeof( Expression ) );
                        Expression rhs = (Expression) info.GetValue( "__rhs", typeof( Expression ) );

                        result = Expression.Divide( lhs, rhs );

                        break;
                    }
                case ExpressionType.And:
                    {
                        Expression lhs = (Expression) info.GetValue( "__lhs", typeof( Expression ) );
                        Expression rhs = (Expression) info.GetValue( "__rhs", typeof( Expression ) );

                        result = Expression.And( lhs, rhs );

                        break;
                    }
                case ExpressionType.Or:
                    {
                        Expression lhs = (Expression) info.GetValue( "__lhs", typeof( Expression ) );
                        Expression rhs = (Expression) info.GetValue( "__rhs", typeof( Expression ) );

                        result = Expression.Or( lhs, rhs );

                        break;
                    }
                case ExpressionType.Equal:
                    {
                        Expression lhs = (Expression) info.GetValue( "__lhs", typeof( Expression ) );
                        Expression rhs = (Expression) info.GetValue( "__rhs", typeof( Expression ) );

                        result = Expression.Equal( lhs, rhs );

                        break;
                    }
                case ExpressionType.NotEqual:
                    {
                        Expression lhs = (Expression) info.GetValue( "__lhs", typeof( Expression ) );
                        Expression rhs = (Expression) info.GetValue( "__rhs", typeof( Expression ) );

                        result = Expression.NotEqual( lhs, rhs );

                        break;
                    }
                case ExpressionType.GreaterThan:
                    {
                        Expression lhs = (Expression) info.GetValue( "__lhs", typeof( Expression ) );
                        Expression rhs = (Expression) info.GetValue( "__rhs", typeof( Expression ) );

                        result = Expression.GreaterThan( lhs, rhs );

                        break;
                    }
                case ExpressionType.GreaterThanOrEqual:
                    {
                        Expression lhs = (Expression) info.GetValue( "__lhs", typeof( Expression ) );
                        Expression rhs = (Expression) info.GetValue( "__rhs", typeof( Expression ) );

                        result = Expression.GreaterThanOrEqual( lhs, rhs );

                        break;
                    }
                case ExpressionType.LessThan:
                    {
                        Expression lhs = (Expression) info.GetValue( "__lhs", typeof( Expression ) );
                        Expression rhs = (Expression) info.GetValue( "__rhs", typeof( Expression ) );

                        result = Expression.LessThan( lhs, rhs );

                        break;
                    }
                case ExpressionType.LessThanOrEqual:
                    {
                        Expression lhs = (Expression) info.GetValue( "__lhs", typeof( Expression ) );
                        Expression rhs = (Expression) info.GetValue( "__rhs", typeof( Expression ) );

                        result = Expression.LessThanOrEqual( lhs, rhs );

                        break;
                    }
                case ExpressionType.Not:
                    {
                        Expression operand = (Expression) info.GetValue( "__operand", typeof( Expression ) );

                        result = Expression.Not( operand );

                        break;
                    }
                case ExpressionType.NegateChecked:
                    {
                        Expression operand = (Expression) info.GetValue( "__operand", typeof( Expression ) );

                        result = Expression.NegateChecked( operand );

                        break;
                    }
                case ExpressionType.ConvertChecked:
                    {
                        Expression operand = (Expression) info.GetValue( "__operand", typeof( Expression ) );
                        Type t = (Type) info.GetValue( "__type", typeof( Type ) );
                        MethodInfo method = (MethodInfo) info.GetValue( "__method", typeof( MethodInfo ) );

                        result = Expression.ConvertChecked( operand, t, method );

                        break;
                    }
                default:

                    Debug.Fail( string.Format( "Expression type not supported: {0}", nodeType.ToString() ) );
                    break;
            }

            return result;
        }
    }
}
